package jmine.tec.database.query;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import jmine.tec.database.query.lazy.Lazy;

import org.apache.commons.lang.StringUtils;
import org.hibernate.jdbc.Work;

/**
 * Uma tabela que se pode consultar.
 * 
 * @author lundberg
 */
public class Table {

    private final Database database;

    private final String name;

    private Lazy<Map<String, Column>> columns;

    private Lazy<List<String>> pkColumns;

    private Lazy<List<ExportedKey>> exportedKeys;

    /**
     * Construtor
     * 
     * @param database database
     * @param tableName tableName
     */
    public Table(Database database, String tableName) {
        this.database = database;
        this.name = tableName;
        this.columns = new Lazy<Map<String, Column>>(new ColumnLoader(database, this));
        this.pkColumns = new Lazy<List<String>>(new PrimaryKeyColumnsLoader(database, this));
        this.exportedKeys = new Lazy<List<ExportedKey>>(new ExportedKeysLoader(database, this));
    }

    /**
     * Busca todas as entradas que estão de acordo com as condições descritas.
     * 
     * @param condition condition
     * @param maxResults maxResults
     * @return List de Entry
     */
    public List<Entry> findEntries(final Condition condition, final int maxResults) {
        final StringBuilder query = new StringBuilder();
        query.append("SELECT * FROM ");
        if (!StringUtils.isEmpty(this.database.getSchema())) {
            query.append(this.database.getSchema());
            query.append(".");
        }
        query.append(this.name);
        if (condition != null) {
            query.append(" WHERE ");
            query.append(condition.createQueryCondition());
        }
        if (!this.pkColumns.get().isEmpty()) {
            query.append(" ORDER BY ");
            query.append(StringUtils.join(this.pkColumns.get(), ", "));
        }
        final List<Entry> entries = new ArrayList<Entry>();
        this.database.doWork(new Work() {
            public void execute(Connection connection) throws SQLException {
                PreparedStatement st = null;
                try {
                    st = connection.prepareStatement(query.toString());
                    if (condition != null) {
                        condition.setParameters(st, 1);
                    }
                    ResultSet rs = st.executeQuery();
                    while (rs.next() && entries.size() < maxResults) {
                        Entry entry = new Entry(Table.this);
                        for (Column column : Table.this.columns.get().values()) {
                            entry.addField(column.getName(), column.read(rs));
                        }
                        entries.add(entry);
                    }
                } finally {
                    DatabaseUtil.closeQuietly(st);
                }
            }
        });
        return entries;
    }

    /**
     * @return name name
     */
    public String getName() {
        return this.name;
    }

    /**
     * @return columns
     */
    public Map<String, Column> getColumns() {
        return Collections.unmodifiableMap(this.columns.get());
    }

    /**
     * @return the exportedKeys
     */
    public List<ExportedKey> getExportedKeys() {
        return this.exportedKeys.get();
    }

    /**
     * Obtém a lista de colunas que compõe a chave primária desta coluna
     * 
     * @return list de string
     */
    public List<String> getPrimaryKeyColumns() {
        return this.pkColumns.get();
    }

    /**
     * Busca a coluna com o nome dado
     * 
     * @param columnName columnName
     * @return Column
     */
    public Column getColumn(String columnName) {
        return this.columns.get().get(columnName);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return String.format("Table:%s,%s", this.database, this.name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Table)) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        Table o = (Table) obj;
        return this.name.equals(o.name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.name.hashCode();
    }
}
